/////////////////////////////////////////////////////////////////////////////
// Name:        ide.cpp
// Author:      Nicolay Giraldo
// Created:     15/12/05
// Copyright:   Nicolay Giraldo
/////////////////////////////////////////////////////////////////////////////

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#  include <wx/wx.h>
#endif

#include <wx/splitter.h>
#include <wx/notebook.h>
#include <wx/spinbutt.h>
//#include <wx/richtext/richtextctrl.h>
#include <wx/textctrl.h>
#include <wx/stream.h>
#include <wx/sstream.h>
#include <wx/regex.h>
#include <wx/datetime.h>
#include <wx/dynarray.h>
#include <wx/config.h>

#include <tommath.h>

#include "types.h"
#include "interpreter.h"
#include "ide.h"
#include "nglisp.h"


wxTextAttr delimitersstyle;
wxTextAttr symbolsstyle   ;
wxTextAttr keywordstyle   ;
wxTextAttr stringsstyle   ;
wxTextAttr floatsstyle    ;
wxTextAttr fixnumsstyle   ;
wxTextAttr bignumsstyle   ;
wxTextAttr defaultstyle   ;
wxTextAttr errorstyle     ;
wxTextAttr hilightrstyle  ;
wxTextAttr hilightgstyle  ;
wxTextAttr hilightbstyle  ;

wxLogColorTextCtrl::wxLogColorTextCtrl(wxTextCtrl *pTextCtrl)
{
    m_pTextCtrl = pTextCtrl;
}

void wxLogColorTextCtrl::DoLogString(const wxChar *szString, time_t WXUNUSED(t))
{
	wxString msg1;
	msg1 << szString;
	wxString msg;
	msg << wxT("\n! ") << msg1.AfterFirst(_T(' '));;
	
	m_pTextCtrl->SetDefaultStyle(errorstyle  );
	m_pTextCtrl->AppendText(msg);
	m_pTextCtrl->SetDefaultStyle(defaultstyle);
}


ide::ide(wxString titulo)
	: wxFrame(NULL, ID_Frame, titulo, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS|wxDEFAULT_FRAME_STYLE)
{
	SetIcon(wxICON(nglisp));
	SetOwnBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE ));
	datoolbar = CreateToolBar();
	dastatusbar = CreateStatusBar(1, wxST_SIZEGRIP);
	
	int	x = wxGetApp().pConfig->Read(_("/Window/x"), 50),
		y = wxGetApp().pConfig->Read(_("/Window/y"), 50),
		w = wxGetApp().pConfig->Read(_("/Window/w"), 620),
		h = wxGetApp().pConfig->Read(_("/Window/h"), 450),
		m = wxGetApp().pConfig->Read(_("/Window/m"), (long)0);
	Move(x, y);
	SetSize(w, h);
	if (m)
	{
		Maximize(true);
	}
	
	splitter = new wxSplitterWindow(this, ID_Splitter, wxDefaultPosition, wxDefaultSize, wxSP_LIVE_UPDATE|wxSP_PERMIT_UNSPLIT);
	splitter->SetMinimumPaneSize(20);
	filesbook = new wxNotebook(splitter, ID_FileNB, wxDefaultPosition, wxDefaultSize, wxNB_BOTTOM);
	filewin = new textEditor(filesbook, ID_FileWin);
	filesbook->AddPage(filewin, _T("buffer"), true);
//	replwin = new replEditor(this, ID_ReplWin);
	replsplitter = new wxSplitterWindow(splitter, ID_ReplWin, wxDefaultPosition, wxDefaultSize, wxSP_LIVE_UPDATE);
	
	response = new textEditor(replsplitter, ID_ReplResponse);
	pprompt  = new wxPanel(replsplitter, ID_ReplPPrompt);
	
	sizer	 = new wxFlexGridSizer(3);
	((wxFlexGridSizer*)sizer)->AddGrowableRow(0, 0);
	((wxFlexGridSizer*)sizer)->AddGrowableCol(1, 0);
	
	prompt = new promptEditor(pprompt, ID_ReplPrompt);
	history = new wxSpinButton(pprompt, ID_ReplHistory);
	send   = new wxButton(pprompt, ID_ReplButton, _T("Eval")/*, wxDefaultPosition, wxSize(10, 10)*/);
	prompt->response = response;
	
	sizer->Add(history, 1, wxEXPAND);
	sizer->Add(prompt , 1, wxEXPAND);
	sizer->Add(send   , 1, wxEXPAND);
	
	pprompt->SetSizer(sizer);
	
	splitter->SetMinimumPaneSize(120);
	splitter->SetSashGravity(1.0);
//	splitter->SplitHorizontally(filesbook, replsplitter);
	splitter->Initialize(replsplitter);
	
	replsplitter->SetMinimumPaneSize(60);
	replsplitter->SetSashSize(1);
	replsplitter->SplitHorizontally(response, pprompt, -60);
	replsplitter->SetSashGravity(1.0);
	
	response->SetDefaultStyle(delimitersstyle);
	response->AppendText(_T("Welcome to ngLisp."));
	response->AppendText(_T("\nEnjoy your REPL."));
	response->SetDefaultStyle(defaultstyle   );
	response->SetEditable(false);
//	response->SetCaretWidth(0);
//	splitter->SplitHorizontally(filesbook, replsplitter);
	
	Connect(ID_ReplHistory, wxEVT_SCROLL_LINEUP, wxSpinEventHandler(ide::OnSpinUp));
	Connect(ID_ReplHistory, wxEVT_SCROLL_LINEDOWN, wxSpinEventHandler(ide::OnSpinDn));
	Connect(ID_ReplButton, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(ide::OnEval));
	
	prompt->SetFocus();
}

ide::~ide()
{
	// save the frame position
	int x, y, w, h;
	GetSize(&w, &h);
	GetPosition(&x, &y);
	if (!IsMaximized())
	{
		wxGetApp().pConfig->Write(_("/Window/x"), (long) x);
		wxGetApp().pConfig->Write(_("/Window/y"), (long) y);
		wxGetApp().pConfig->Write(_("/Window/w"), (long) w);
		wxGetApp().pConfig->Write(_("/Window/h"), (long) h);
	}
	wxGetApp().pConfig->Write(_("/Window/m"), (long) IsMaximized());
}

textEditor::textEditor (wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style)
    : wxTextCtrl (parent, id, _T(""), pos, size, style)
{
	SetFont(wxFont(11, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, _T("Lucida Console")));
//	BeginSuppressUndo();
}


void 
parens::findmatch(int n)
{
	wxString val = parent->GetValue();
	int len = val.Len();
	int matched = n;
	int index = 0;
	if (start != -1)
	{
		for (index = start+1; index<len ; index++ )
		{
			switch (val[index])
			{
			case _T('('): matched++; break;
			case _T(')'): matched--; break;
			}
			if (matched == 0)
			{
				end = index;
				break;
			}
		}
		if (end == -1)
		{
			start = -1;
		}
	}
	matched = n;
	if (end != -1)
	{
		for (index = end-1; index>-1 ; index-- )
		{
			switch (val[index])
			{
			case _T(')'): matched++; break;
			case _T('('): matched--; break;
			}
			if (matched == 0)
			{
				start = index;
				break;
			}
		}
		if (start == -1)
		{
			end = -1;
		}
	}
}


void 
parens::clearhighlighting()
{
	if (start != -1)
	{
		parent->SetStyle(start, start+1, defaultstyle);
	}
	if (end != -1)
	{
		parent->SetStyle(end, end+1, defaultstyle);
	}
//	parent->SetStyle(0, parent->GetLastPosition(), defaultstyle);
}


void 
parens::performhighlighting()
{
	if (start != -1)
	{
		parent->SetStyle(start, start+1, textattr);
	}
	if (end != -1)
	{
		parent->SetStyle(end, end+1, textattr);
	}
}


promptEditor::promptEditor (wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style)
	: wxTextCtrl (parent, id, _T(""), pos, size, style),
	  prevparens(this, hilightrstyle), nextparens(this, hilightbstyle), outrparens(this, hilightgstyle)
{
	SetFont(wxFont(11, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, _T("Lucida Console")));
//	BeginSuppressUndo();
	Connect(-1, wxEVT_KEY_DOWN , wxKeyEventHandler(promptEditor::OnKeyDown));
	Connect(-1, wxEVT_KEY_UP   , wxKeyEventHandler(promptEditor::OnKeyUp  ));
	Connect(-1, wxEVT_LEFT_DOWN, wxMouseEventHandler(promptEditor::OnMouseDn));
	Connect(-1, wxEVT_LEFT_UP  , wxMouseEventHandler(promptEditor::OnMouseUp));
	dareader = new reader();
	long hnumber = wxGetApp().pConfig->Read(_("/History/number"), 0l);
	if (hnumber)
	{
		history.Add(_(""), hnumber);
		wxString nnumber;
		for ( int i = hnumber; i; i--)
		{
			nnumber.Printf(_("/History/n%03d"), i-1);
			history[i-1] = wxGetApp().pConfig->Read(nnumber, _(""));
		}
		hindex = hnumber;
	}
	else
	{
		hindex = -1;
	}
}


promptEditor::~promptEditor ()
{
	if (history.GetCount() > 500)
	{
		history.RemoveAt(0, history.GetCount() - 500);
	}
	wxGetApp().pConfig->Write(_("/History/number"), (long) history.GetCount());
	wxString nnumber;
	for ( int i = history.GetCount(); i; i--)
	{
		nnumber.Printf(_("/History/n%03d"), i-1);
		wxGetApp().pConfig->Write(nnumber, history[i-1]);
	}
}


void 
promptEditor::OnKeyDown (wxKeyEvent &event)
{
	GetSelection(&from, &to);
	prevparens.clearhighlighting();
	nextparens.clearhighlighting();
	outrparens.clearhighlighting();
	
	SetDefaultStyle(defaultstyle);
	switch (event.GetKeyCode())
	{
	case WXK_RETURN:
		if (event.ControlDown())
		{
			DoEval(); return;
		}
		if (event.ShiftDown())
		{
			event.Skip();
			return;
		}
		else
		{
			value* readstuff;
			{
				wxLogNull ln;
				wxString istring = this->GetValue();
				wxStringInputStream istream(istring);
				dareader->haschar = false;
				dareader->init();
				readstuff = dareader->read(istream);
			}
			if (readstuff)
			{
				DoEval(); return;
			}
		}
		break;
	case WXK_UP:
		if (event.ControlDown())
		{
			DoPrevText(); return;
		}
		break;
	case WXK_DOWN:
		if (event.ControlDown())
		{
			DoNextText(); return;
		}
		break;
	}
	event.Skip();
}


void 
promptEditor::OnKeyUp (wxKeyEvent &event)
{
	event.Skip();
	
	prevparens.clear();
	nextparens.clear();
	outrparens.clear();
	int previdx = -1, thisidx = -1;
	wxChar prevchar = _T('\0'), thischar = _T('\0');
	wxString val = this->GetValue();
	if (this->GetInsertionPoint() > 0)
	{
		int inspoint = this->GetInsertionPoint() - 1;
		prevchar = val[inspoint];
		previdx = inspoint;
	}
	if (this->GetInsertionPoint() < this->GetLastPosition())
	{
		int inspoint = this->GetInsertionPoint();
		thischar = val[inspoint];
		thisidx = inspoint;
	}
	
	if (prevchar == _T(')'))
	{
		prevparens.setend(previdx);
		prevparens.findmatch(1);
	}
	if (thischar == _T('('))
	{
		nextparens.setstart(thisidx);
		nextparens.findmatch(1);
	}
	if (prevchar == _T('('))
	{
		outrparens.setstart(previdx);
		outrparens.findmatch(1);
	}
	if (thischar == _T(')'))
	{
		outrparens.setend(thisidx);
		outrparens.findmatch(1);
	}
	if (outrparens.start == -1 && nextparens.start == -1 && thischar != _T(')'))
	{
		outrparens.setstart(thisidx);
		outrparens.findmatch(1);
	}
	
	prevparens.performhighlighting();
	nextparens.performhighlighting();
	outrparens.performhighlighting();
	
//	SetSelection(from, to);
}


void 
promptEditor::OnMouseDn (wxMouseEvent &event)
{
	GetSelection(&from, &to);
	prevparens.clearhighlighting();
	nextparens.clearhighlighting();
	outrparens.clearhighlighting();
	
	event.Skip();
}


void 
promptEditor::OnMouseUp (wxMouseEvent &event)
{
	event.Skip();
	
	prevparens.clear();
	nextparens.clear();
	outrparens.clear();
	int previdx = -1, thisidx = -1;
	wxChar prevchar = _T('\0'), thischar = _T('\0');
	wxString val = this->GetValue();
	if (this->GetInsertionPoint() > 0)
	{
		int inspoint = this->GetInsertionPoint() - 1;
		prevchar = val[inspoint];
		previdx = inspoint;
	}
	if (this->GetInsertionPoint() < this->GetLastPosition())
	{
		int inspoint = this->GetInsertionPoint();
		thischar = val[inspoint];
		thisidx = inspoint;
	}
	
	if (prevchar == _T(')'))
	{
		prevparens.setend(previdx);
		prevparens.findmatch(1);
	}
	if (thischar == _T('('))
	{
		nextparens.setstart(thisidx);
		nextparens.findmatch(1);
	}
	if (prevchar == _T('('))
	{
		outrparens.setstart(previdx);
		outrparens.findmatch(1);
	}
	if (thischar == _T(')'))
	{
		outrparens.setend(thisidx);
		outrparens.findmatch(1);
	}
	if (outrparens.start == -1 && nextparens.start == -1 && thischar != _T(')'))
	{
		outrparens.setstart(thisidx);
		outrparens.findmatch(1);
	}
	
	prevparens.performhighlighting();
	nextparens.performhighlighting();
	outrparens.performhighlighting();
	
//	SetSelection(from, to);
}


void 
ide::OnEval(wxCommandEvent& WXUNUSED(event))
{
	prompt->DoEval();
}


void 
ide::OnSpinUp(wxSpinEvent& WXUNUSED(event))
{
	prompt->DoPrevText();
}


void 
ide::OnSpinDn(wxSpinEvent& WXUNUSED(event))
{
	prompt->DoNextText();
}


void 
promptEditor::DoNextText()
{
	if ((hindex < (int)(history.GetCount()-1)) && (hindex != -1))
	{
		this->SetValue(history[++hindex]);
		this->SetInsertionPointEnd();
	}
	else wxBell();
}


void 
promptEditor::DoPrevText()
{
	if (hindex && hindex != -1)
	{
		this->SetValue(history[--hindex]);
		this->SetInsertionPointEnd();
	}
	else wxBell();
}


void 
promptEditor::DoEval()
{
//	MutexGuiLocker mguilocker;
	if (this->GetValue().IsEmpty())
	{
		wxBell();
		return;
	}
	
	wxString* istring = new wxString(this->GetValue());
	
	completeparens(istring);
	
	history.Add(*istring);
	hindex = history.GetCount();
	
	this->Clear();
	this->SetFocus();
	
//	ngLispRuntime->createReplThread();
//	ngLispRuntime->mainrepl->istring = istring;
//	ngLispRuntime->mainrepl->response = response;
//	ngLispRuntime->mainrepl->guioutput = true;
//	ngLispRuntime->mainrepl->Run();
	
	wxStringInputStream istream(*istring);
	wxString ostring;
	wxStringOutputStream ostream(&ostring);
	wxString printstr;
	value* readstuff;
	value* evalstuff;
	
	bool guioutput = true;
	// do the stuff
	dareader->haschar = false;
	dareader->init();
	ostring.Empty();
	if (guioutput)
	{
//		MutexGuiLocker mguilocker;
		response->SetDefaultStyle(delimitersstyle);
		printstr.Printf(_T("\n> "));
		response->AppendText(printstr);
		response->SetDefaultStyle(defaultstyle   );
//		printstr.Printf(_T("\n> %s"), istring->c_str());
		response->AppendText(*istring);
//		guiprint(readstuff, *response);
	}
//	else
//	{
//		ostream->Write(printstr.c_str(), printstr.Len());
//	}
	readstuff = dareader->read(istream);
	do
	{
//		print(eval(readstuff, NULL), guiostream);
//		printstr.Printf(_T("\n= %s"), ostring.c_str());
		if (guioutput)
		{
//			MutexGuiLocker mguilocker;
			evalstuff = eval(readstuff, NULL);
			wxLog::FlushActive();
			response->SetDefaultStyle(delimitersstyle);
			response->AppendText(_T("\n= "));
			response->SetDefaultStyle(defaultstyle   );
			guiprint(evalstuff, *response);
//			response->AppendText(printstr);
		}
		else
		{
			print(eval(readstuff, NULL), ostream);
//			ostream->Write(printstr.c_str(), printstr.Len());
		}
		dareader->init();
		ostring.Empty();
		readstuff = dareader->read(istream);
	}
	while (istream.CanRead());//!( || readstuff)
	if (readstuff)
	{
//		print(eval(readstuff, NULL), guiostream);
//		printstr.Printf(_T("\n= %s"), ostring.c_str());
		if (guioutput)
		{
//			MutexGuiLocker mguilocker;
			evalstuff = eval(readstuff, NULL);
			response->SetDefaultStyle(delimitersstyle);
			response->AppendText(_T("\n= "));
			response->SetDefaultStyle(defaultstyle   );
			guiprint(evalstuff, *response);
//			response->AppendText(printstr);
		}
		else
		{
			print(eval(readstuff, NULL), ostream);
//			ostream->Write(printstr.c_str(), printstr.Len());
		}
	}
	if (guioutput)
	{
		// ir al final del texto 
//		MutexGuiLocker mguilocker;
		response->SetInsertionPointEnd();
//		int sx, sy, scx, scy;
//		response->GetVirtualSize(&sx, &sy);
//		response->GetScrollPixelsPerUnit(&scx, &scy);
//		response->Scroll(-1, sy*(scy+1));
	}
}

void 
init_text_attr(void)
{
	delimitersstyle.SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
	symbolsstyle   .SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
	keywordstyle   .SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
	stringsstyle   .SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
	floatsstyle    .SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
	fixnumsstyle   .SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
	bignumsstyle   .SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
	defaultstyle   .SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
	errorstyle     .SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
	hilightrstyle  .SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
	hilightgstyle  .SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
	hilightbstyle  .SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
	delimitersstyle.SetTextColour(wxTheColourDatabase->Find(_T("SEA GREEN")));
	symbolsstyle   .SetTextColour(wxTheColourDatabase->Find(_T("MEDIUM BLUE")));
	keywordstyle   .SetTextColour(wxTheColourDatabase->Find(_T("DARK ORCHID")));
	stringsstyle   .SetTextColour(wxTheColourDatabase->Find(_T("MEDIUM VIOLET RED")));
	floatsstyle    .SetTextColour(wxTheColourDatabase->Find(_T("SEA GREEN")));
	fixnumsstyle   .SetTextColour(wxTheColourDatabase->Find(_T("SLATE BLUE")));
	bignumsstyle   .SetTextColour(wxTheColourDatabase->Find(_T("SKY BLUE")));
	defaultstyle   .SetTextColour(wxTheColourDatabase->Find(_T("BLACK")));
	errorstyle     .SetTextColour(wxTheColourDatabase->Find(_T("RED")));
	hilightrstyle  .SetTextColour(wxTheColourDatabase->Find(_T("LIGHT MAGENTA")));
	hilightgstyle  .SetTextColour(wxTheColourDatabase->Find(_T("GREEN")));
	hilightbstyle  .SetTextColour(wxTheColourDatabase->Find(_T("BLUE")));
}


void 
completeparens(wxString* s_expr)
{
	int parens = 0;
	// This code doesn't work properly with GCC, but works with VC++   (to report bug to GCC bug list)
//	wxChar rchar;
//	wxStringInputStream sistream(*s_expr);
//	do
//	{
//		rchar = sistream.GetC();
//		if (rchar == _T('(')) parens++;
//		if (rchar == _T(')')) parens--;
//	}
//	while (sistream.LastRead());
	for (unsigned int i = 0; i < s_expr->Length() ; i++)
	{
		switch (s_expr->GetChar(i))
		{
		case _T('('): parens++; break;
		case _T(')'): parens--; break;
		}
	}
	if (parens > 0)
	{
		s_expr->Append(_T(')'), parens);
	}
}
